/*
 * Jonathan Marokhovsky
 * WPI
 * Version: November 27, 2011
 * 
 * Nutella Listener Process
 */
#include "listener.h"
#include <sys/ioctl.h>
#include <net/if.h>

#define TCP_PORT	"8000"
#define PORT_WAIT	5 // how long to wait with the TCP port open in seconds

#define MOV_LIST	50 // how many movies are allowed in the directory
#define LIST_LOC	"./.nutella"
/* Assuming that all movie files will be in the same folder as the .nutella file */

/* For playing the movie */
#define MAX_LINE	300
#define MAX_NAME	100
#define FRAME_RATE	100000 // frame rate in micro seconds
#define ACCEPT_WAIT	100000 // If getting the error "accept: Resource temporarily unavailable" increase this value
#define REPLAY_WAIT	10 // how long to wait for the user to reply 
#define WRONG_MAX	10 /* If the wrong message was received more than this
						 * then assume the right answer isn't coming.
						 */

int search(char *search, char movieList[MOV_LIST][MAX_SEARCH], int listMax);
int getIP(char addr[IP_LEN]);
int createStream(int port);
int waitAndListen(int socket);
int playToSocket(int socket, char *theFile);

/*
 * The listener listens on the Query port for movie requests and then streams
 * the movies which it has to one user
 */
int listener()
{
	int qSock, rSock, tcpSock, streamSock, found, bytesRecv, repFlag, wrongCounter;
	int listMax = 0;
	char query[MAX_SEARCH];
	char ipAddr[IP_LEN];
	char response[MAX_RESP];
	char movieList[MOV_LIST][MAX_SEARCH];
	char handshake[MAX_SEARCH];
	char handshakeResp[MAX_RESP];
	FILE *movieFile;
	fd_set readFrom;
	struct timeval timeOut, replayTimeOut;
	
	timeOut.tv_sec = PORT_WAIT;
	timeOut.tv_usec = 0;
	replayTimeOut.tv_sec = REPLAY_WAIT;
	replayTimeOut.tv_usec = 0;
		
	if (debug) printf("LISTENER: In the listener function now... \n");
	
	/* Go through the .nutella config file and find out what movies are there */
	if (debug) printf("LISTENER: Populating the movie list.\n");
	if (debug) printf("LISTENER: Opening the .nutella config file at:\n");
	if (debug) printf("LISTENER:\t'%s'\n", LIST_LOC);
	if ((movieFile = fopen(LIST_LOC, "r")) == NULL)
	{
		perror("ERROR: opening file");
		exit(EXIT_FAILURE);
	}
	else
	{
		while (listMax < MOV_LIST 
			&& (fgets(movieList[listMax], MAX_SEARCH + 1, movieFile)) != NULL)
		{
			movieList[listMax][strlen(movieList[listMax]) - 1] = '\0'; // remove the newline
			if (debug) printf("LISTENER: Read '%s' and stored it in memory\n", movieList[listMax]);
			listMax++;
		}
		if (debug) printf("LISTENER: Closing file.\n");
		if (fclose(movieFile))
		{
			perror("ERROR: closing file");
			exit(EXIT_FAILURE);
		}
	}
	
	/* Get the IP address before the loop starts so it is only done once */
	if (debug) printf("LISTENER: Getting the IP address of this machine.\n");
	if (getIP(ipAddr) < 0)
	{
		perror("ERROR: getting IP address");
		exit(EXIT_FAILURE);
	}
	if (debug) printf("LISTENER: This machine's IP address is: '%s'\n", ipAddr);
	
	/* Open all necessary sockets */
	/* Open the Query socket */
	if (debug) printf("LISTENER: Opening query socket.\n");
	if ((qSock = msockcreate(RECV, Q_ADDR, Q_PORT)) < 0)
	{
		perror("ERROR: query socket creation");
		exit(EXIT_FAILURE);
	}
	/* Open the Response socket */
	if (debug) printf("LISTENER: Opening response socket.\n");
	if ((rSock = msockcreate(SEND, R_ADDR, R_PORT)) < 0)
	{
		perror("ERROR: response socket creation");
		exit(EXIT_FAILURE);
	}
	/* Open the TCP socket */
	if (debug) printf("LISTENER: Opening streaming socket.\n");
	if ((tcpSock = createStream(atoi(TCP_PORT))) < 0)
	{
		perror("ERROR: streaming socket creation");
		exit(EXIT_FAILURE);
	}
	/* Make the TCP socket nonblocking so it can timeout */
	if (debug) printf("CLIENT: Setting the Receive socket to nonblocking.\n");
	if (fcntl(tcpSock, F_SETFL, O_NONBLOCK) < 0)
	{
		perror("ERROR: set nonblocking");
		exit(EXIT_FAILURE);
	}
	
	/* Start the listening loop */
	while(1)
	{
		/* Listen on the Query socket */
		if (debug) printf("LISTENER: Listening on socket. \n");
		if (mrecv(qSock, query, MAX_SEARCH + 1) < 0)
		{
			perror("ERROR: receiving query");
			exit(EXIT_FAILURE);
		}
		
		/* 
		 * If a Query is found search in the movie array to see if the movie
		 * title is there.
		 */
		if (debug) printf("LISTENER: Received the request for '%s'.\n", query);
		if (debug) printf("LISTENER: Searching for '%s'...\n", query);
		if (search(query, movieList, listMax) != 0)
		{
			/* If you don't have it, return to the start of the loop */
			if (debug) printf("LISTENER: '%s' was not found\n", query);
		}
		else
		{
			/* 
			 * If you do have the movie, send the IP and TCP information through the
			 * response socket.
			 */
			if (debug) printf("LISTENER: Found '%s'!\n", query);
			
			/* Make the response message */
			if (debug) printf("LISTENER: Making the response message.\n");
			strncpy(response, query, strlen(query) + 1); // put the query in the response to differentiate
			strcat(response, DELIM); // separate with whatever is defined as DELIM
			strcat(response, ipAddr); // IP address
			strcat(response, DELIM);
			strcat(response, TCP_PORT); // Port
			
			sleep(1); // sleep for a second so the client has time to set up their receive port
			
			/* Send the response message */
			if (debug) printf("LISTENER: The response is '%s'\n", response);
			if (debug) printf("LISTENER: Sending the response.\n");
			if (msend(rSock, response, strlen(response) + 1) < 0)
			{
				perror("ERROR: sending response");
				exit(EXIT_FAILURE);
			}
						
			/* Listen on the TCP socket for the set amount of time for a response. */
			/* Add the TCP socket to the readset */
			if (debug) printf("LISTENER: Adding the Receive socket to a receive set.\n");
			FD_ZERO(&readFrom);
			if (FD_SET(tcpSock, &readFrom) < 0)
			{
				perror("ERROR: adding tcpsock to readset");
				exit(EXIT_FAILURE);
			}
			if (debug) printf("LISTENER: Waiting for a TCP response\n");
			bytesRecv = select(tcpSock + 1, &readFrom, NULL, NULL, &timeOut);
			if (bytesRecv == 0) // if select returns 0 it means it timed out.
			{
				/* If it times out just start the listen loop all over again */
				if (debug) printf("LISTENER: Select returned 0.\n");
				if (debug) printf("Connection timed out.\n");
			}
			else
			{
				/* Select detected a connection request */
				if (debug) printf("LISTENER: Select returned %d\n", bytesRecv);
				if (FD_ISSET(tcpSock, &readFrom))
				{
					/* Check the response and connect if possible */
					if ((streamSock = waitAndListen(tcpSock)) < 0)
					{
						perror("ERROR: listening for response");
						exit(EXIT_FAILURE);
					}
					else
					{
						do
						{
							repFlag = 0;
							/* Now that it's connected, start streaming the movie */
							if (debug) printf("LISTENER: Now connected to a client\n");
							if (playToSocket(streamSock, query) < 0)
							{
								perror("ERROR: playing movie");
								exit(EXIT_FAILURE);
							}
							if (debug) printf("LISTENER: The movie finished playing.\n");
							
							/* Send over the socket fd as half a handshake */
							if (debug) printf("LISTENER: Trying to send over the handshake\n");
							if (debug) printf("LISTENER: The value of streamSock is %d\n", streamSock);
							if (sprintf(handshake, "%d", streamSock) < 0)
							{
								perror("ERROR: Converting streamSock to handshake");
								exit(EXIT_FAILURE);
							}
							if (debug) printf("LISTENER: Sending over %s as a handshake.\n", handshake);
							usleep(ACCEPT_WAIT); // waiting incase there is a race condition
							if (write(streamSock, handshake, MAX_SEARCH) == -1)
							{
								perror("ERROR: failed to write");
								exit(EXIT_FAILURE);
							}
							if (debug) printf("LISTENER: Successfully sent handshake.\n");
							wrongCounter = 0;
							do
							{
								/* If select() returns 0 it means it timed out */
								if (debug) printf("LISTENER: Checking for the response\n");
								FD_ZERO(&readFrom);
								if (FD_SET(streamSock, &readFrom) < 0)
								{
									perror("ERROR: adding tcpsock to readset");
									exit(EXIT_FAILURE);
								}
								bytesRecv = select(streamSock + 1, &readFrom, NULL, NULL, &replayTimeOut);
								if (bytesRecv > 0)
								{
									if (debug) printf("LISTENER: Got a response\n");
									if (FD_ISSET(streamSock, &readFrom))
									{
										if (read(streamSock, handshakeResp, MAX_RESP) < 0)
										{
											perror("ERROR: receiving handshake response");
											exit(EXIT_FAILURE);
										}
										if (debug) printf("LISTENER: The response was: %s", handshakeResp);
										/* For every response, look for the handshake */
										if (strncmp(handshake, handshakeResp, strlen(handshake) + 1) == 0)
										{
											if (debug) printf("LISTENER: Correct response! replaying\n");
											/* If you get the handshake back within the time allotted, start replaying */
											repFlag++;
											break;
										}
										else
										{
											if (debug) printf("LISTENER: Incorrect response, incrementing wrong count\n");
											// If it's the wrong response, increment the wrong counter
											wrongCounter++;
										}
									}
								}
								else if (bytesRecv == 0)
								{
									/* Otherwise the client doesn't want to repeat */
									if (debug) printf("LISTENER: Timed out\n");
									break;
								}
							} while ((bytesRecv != 0) || (wrongCounter < WRONG_MAX));
						} while (repFlag > 0);
							if (debug) printf("LISTENER: Closing the stream socket.\n");
							if (close(streamSock) < 0)// close the stream session
							{
								perror("ERROR: closing stream socket");
								exit(EXIT_FAILURE);
							}
					}
				}
			}
		}	
		/* return to the start of the loop */
	}
	/* Close all sockets outside of the loop */
	/* Close the Query socket */
	if (debug) printf("LISTENER: Closing query socket.\n");
 	if (msockdestroy(qSock) < 0)// close the Query socket
 	{
 		perror("ERROR: query socket destruction");
 		exit(EXIT_FAILURE);
 	}
	
	/* Close the response socket */
	if (debug) printf("LISTENER: Closing the response socket.\n");
	if (msockdestroy(rSock) < 0)// close the Response socket
 	{
 		perror("ERROR: response socket destruction");
 		exit(EXIT_FAILURE);
 	}
	
	/* Close the TCP socket*/
	if (debug) printf("LISTENER: Closing the tcp socket.\n");
	if (close(tcpSock) < 0)
	{
		perror("ERROR: closing tcp socket");
		exit(EXIT_FAILURE);
	}	
	if (debug) printf("LISTENER: Exiting the listener... \n");
	return 0; // indicate the program succeeded
}

/*
 * search() runs through the given array of string arrays and returns 0 if a
 * match is found to the search param, 1 otherwise.
 *
 * NOTE: The query is case sensitive.
 */
int search(char *search, char movieList[MOV_LIST][MAX_SEARCH], int listMax)
{
	int i;
	for (i = 0; i < listMax; i++)
	{
		if (strncmp(search, movieList[i], strlen(search)) == 0)
		{
			return 0;
		}
	}
	return 1;
}

/*
 * getIP() returns 0 if the IP was successfully given or -1 if there was an
 * error.  The IP will be stored in the addr argument.
 *
 * This assumes the machine is in IPv4 and has a wired connection on a linux box.
 *
 * This function is based on code from:
 * 		http://www.cnx-software.com/2011/04/05/c-code-to-get-mac-address-and-ip-address/
 */
int getIP(char addr[IP_LEN])
{
	int sock;
	int ret = 0;
	struct ifreq ifr;
	
	/* Make a dummy socket */
	if ((sock = socket(AF_INET, SOCK_DGRAM, 0)) < 0)
	{
		perror("ERROR: open socket");
		ret = EXIT_FAILURE;
	}
	else
	{
		/*
		 * Use ioctl() to determine the IP address of the socket which is on the
		 * machine, thus the machine's IP
		 */
		strcpy(ifr.ifr_name, "eth0"); // assume it's a wired connection on the ccc boxes
		if (ioctl(sock, SIOCGIFADDR, &ifr) < 0)
		{
			strcpy(addr, "0.0.0.0");
			perror("ERROR: reading ip");
			ret = EXIT_FAILURE;
		}
		sprintf(addr, "%s", inet_ntoa(((struct sockaddr_in *) &(ifr.ifr_addr))->sin_addr));
		close(sock);
	}
	return ret;
}

/*
 * createStream() opens a TCP stream and binds it with the given port.
 *
 * it returns the socket or -1 if it failed
 *
 * NOTE: this was taken from my Project 2 server.c nearly word for word
 */
int createStream(int port)
{
	int sock;
	struct sockaddr_in addr;
	
	if ((sock = socket(AF_INET, SOCK_STREAM, 0)) < 0)
	{
		perror("ERROR: socket creation");
	}
	else
	{
		bzero((char *) &addr, sizeof(addr));
		addr.sin_family = AF_INET;
		addr.sin_addr.s_addr = htonl(INADDR_ANY);
		addr.sin_port = htons(port);
		if ((bind(sock, (struct sockaddr *) &addr, sizeof(addr))) < 0)
		{
			perror("ERROR: can't bind to local address");
			close(sock);
			sock = EXIT_FAILURE;
		}
	}
	return sock;
}

/*
 * waitAndListen() waits a certain amount of time and either connects to a
 * nutella client or nothing
 * It returns either the socket the client connected at, 0 meaning it timed out,
 * or -1 if there was an error.
 *
 * Note: this was taken from my Project 2 server.c nearly word for word
 */
int waitAndListen(int socket)
{
	int sock = 0;
	int clientLen;
	struct sockaddr_in clientAddr;
	
	clientLen = sizeof(clientAddr);	
	listen(socket, 5);
	usleep(ACCEPT_WAIT); // putting this here to help the accept() catch up
	if ((sock = accept(socket, (struct sockaddr *) &clientAddr, &clientLen)) < 0)
	{
		perror("ERROR: accept");
		sock = EXIT_FAILURE;
	}
	return sock;
}

/*
 * playToSocket() plays the given movie to the given socket
 *
 * It returns 0 if it executed correctly or -1 otherwise
 */
int playToSocket(int socket, char *theFile)
{
	int endOfFrame, eofFlag, ret;
	char frame[MAX_FRAME];
	char temp[MAX_LINE];
	char textEnd[] = ".txt";
	char fileBegin[] = "./";
	char fileName[MAX_NAME];
	FILE *movieFile;
	
	ret = 0;
	
	/* Make the correct file name */
	strcpy(fileName, fileBegin);
	strcat(fileName, theFile);
	strcat(fileName, textEnd);
	if (debug) printf("LISTENER: The new file name is '%s'\n", fileName);
	
	/* Open the file */
	if ((movieFile = fopen(fileName, "r")) == NULL)
	{
		perror("ERROR: opening file");
		ret = EXIT_FAILURE;
	}
	else
	{
		/* The file is no open, start reading */
		eofFlag = 0;
		while (eofFlag == 0)
		{
			frame[0] = '\0';
			endOfFrame = 0;
			while (endOfFrame == 0)
			{
				/* Read a line */
				if (fgets(temp, MAX_LINE + 1, movieFile) == (char *) NULL)
				{
					/* If it's the end of the file, set the eofFlag */
					if (debug) printf("LISTENER: End of file reached\n");
					eofFlag++;
				}
				else
				{
					/* If it's "end" set a flag, otherwise add to frame */
					if (strncmp(temp, "end", 3) == 0)
					{
						endOfFrame++;
					}
					else
					{
						strncat(frame, temp, strlen(temp));
					}
				}
				if (eofFlag > 0) break;
			}
			/* If the end of the file has not been reached, send the frame */
			if (eofFlag == 0)
			{
				/* Send what you read at the desired frame rate */
				usleep(FRAME_RATE);
				if (write(socket, frame, strlen(frame) + 1) == -1)
				{
					perror("ERROR: failed to write");
					ret = EXIT_FAILURE;
				}
			}
			else
			{
				if (debug) printf("LISTENER: Made it past the write\n");
			}
			if (eofFlag > 0 && debug) printf("LISTENER: Getting out of EOF loop\n");
		}
		if (debug) printf("LISTENER: Past the EOF loop \n");
		if (debug) printf("LISTENER: Printing NULL to signify the end of the movie\n");
		if (write(socket, "end", 3) == -1)
		{
			perror("ERROR: failed to write null");
			ret = EXIT_FAILURE;
		}
		/* Close the file */
		if (fclose(movieFile))
		{
			perror("ERROR: closing file");
			ret = EXIT_FAILURE;
		}
		if (debug) printf("LISTENER: File closed, writing Null\n");
	}
	if (debug) printf("LISTENER: Exiting...\n");
	return ret;
}
